﻿namespace FastDynamicMemberAccessor
{
    using System;
    using System.Reflection;
    using System.Reflection.Emit;

    internal class PropertyAccessor : MemberAccessor
    {
        private readonly bool _canRead;
        private readonly bool _canWrite;
        private readonly Type _propertyType;

        internal PropertyAccessor(PropertyInfo info) : base(info)
        {
            this._canRead = info.CanRead;
            this._canWrite = info.CanWrite;
            this._propertyType = info.PropertyType;
        }

        protected override void _EmitGetter(TypeBuilder myType)
        {
            Type[] parameterTypes = new Type[] { typeof(object) };
            Type returnType = typeof(object);
            ILGenerator iLGenerator = myType.DefineMethod("Get", MethodAttributes.Virtual | MethodAttributes.Public, returnType, parameterTypes).GetILGenerator();
            MethodInfo method = base._targetType.GetMethod("get_" + base._fieldName);
            if (method != null)
            {
                iLGenerator.DeclareLocal(typeof(object));
                iLGenerator.Emit(OpCodes.Ldarg_1);
                iLGenerator.Emit(OpCodes.Castclass, base._targetType);
                iLGenerator.EmitCall(OpCodes.Call, method, null);
                if (method.ReturnType.IsValueType)
                {
                    iLGenerator.Emit(OpCodes.Box, method.ReturnType);
                }
                iLGenerator.Emit(OpCodes.Stloc_0);
                iLGenerator.Emit(OpCodes.Ldloc_0);
            }
            else
            {
                iLGenerator.ThrowException(typeof(MissingMethodException));
            }
            iLGenerator.Emit(OpCodes.Ret);
        }

        protected override void _EmitSetter(TypeBuilder myType)
        {
            Type[] parameterTypes = new Type[] { typeof(object), typeof(object) };
            Type returnType = null;
            ILGenerator iLGenerator = myType.DefineMethod("Set", MethodAttributes.Virtual | MethodAttributes.Public, returnType, parameterTypes).GetILGenerator();
            MethodInfo method = base._targetType.GetMethod("set_" + base._fieldName);
            if (method != null)
            {
                Type parameterType = method.GetParameters()[0].ParameterType;
                iLGenerator.DeclareLocal(parameterType);
                iLGenerator.Emit(OpCodes.Ldarg_1);
                iLGenerator.Emit(OpCodes.Castclass, base._targetType);
                iLGenerator.Emit(OpCodes.Ldarg_2);
                if (parameterType.IsValueType)
                {
                    iLGenerator.Emit(OpCodes.Unbox, parameterType);
                    if (MemberAccessor.s_TypeHash[parameterType] != null)
                    {
                        OpCode opcode = (OpCode) MemberAccessor.s_TypeHash[parameterType];
                        iLGenerator.Emit(opcode);
                    }
                    else
                    {
                        iLGenerator.Emit(OpCodes.Ldobj, parameterType);
                    }
                }
                else
                {
                    iLGenerator.Emit(OpCodes.Castclass, parameterType);
                }
                iLGenerator.EmitCall(OpCodes.Callvirt, method, null);
            }
            else
            {
                iLGenerator.ThrowException(typeof(MissingMethodException));
            }
            iLGenerator.Emit(OpCodes.Ret);
        }

        internal override bool CanRead
        {
            get
            {
                return this._canRead;
            }
        }

        internal override bool CanWrite
        {
            get
            {
                return this._canWrite;
            }
        }

        internal override Type MemberType
        {
            get
            {
                return this._propertyType;
            }
        }
    }
}

